import math
import random
import numpy as np
from sklearn.cluster import KMeans
from skimage import io, color
import kociemba
import packages.move as move
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
class Cube:
# the squares on the image taken which contain one piece of the cube
cube_string = []
pixel_groups = [ # the pixel positions each face of the cube shows up on the camera
(163, 0, 185, 34),
(341, 0, 430, 34),
(568, 0, 627, 34),
(71, 140, 160, 300),
(341, 140, 430, 300),
(624, 140, 700, 300),
(154, 413, 190, 472),
(333, 422, 424, 472),
(573, 417, 622, 476),
]
# rotates any list by 90 degrees as if it were a cube face.
# can accept any number of rotations, positive / negative and recursively calls itself to complete them
def rotate_slist(self, side_list, rotate_num):
rotate_num = rotate_num % 4
if rotate_num != 0:
new_side_list = [
side_list[6], side_list[3], side_list[0],
side_list[7], side_list[4], side_list[1],
side_list[8], side_list[5], side_list[2],
]
rotate_num -= 1
if rotate_num != 0:
new_side_list = self.rotate_slist(new_side_list, rotate_num)
return new_side_list
else:
return side_list
def rotate_side(self, side, rotate_num):
rotate_num = rotate_num % 4
if rotate_num != 0:
side_dict = {
'U': 0,
'R': 1,
'F': 2,
'D': 3,
'L': 4,
'B': 5,
}
# brute force way of accessing the right faces. It's terrible, but it works
# structured by each subface going clockwise represented by the face number
# the second is the orientation of the 3 stickers on the subface
# the third is the orientation of the move if the face is rotated clockwise
sub_face_dict = {
'U': [('B', 0, 1), ('R', 0, 1), ('F', 0, 1), ('L', 0, 1)],
'R': [('U', 1, 1), ('B', 3, 1), ('D', 1, 1), ('F', 1, 1)],
'F': [('U', 2, 1), ('R', 3, 1), ('D', 0, 1), ('L', 1, 1)],
'D': [('F', 2, 1), ('B', 2, 1), ('D', 2, 1), ('F', 2, 1)],
'L': [('U', 3, 1), ('F', 3, 1), ('D', 3, 1), ('B', 1, 1)],
'B': [('U', 0, 1), ('L', 3, 1), ('D', 2, 1), ('R', 1, 1)],
}
sub_face_access_dict = {
0: [0, 1, 2],
1: [2, 5, 8],
2: [8, 7, 6],
3: [6, 4, 0],
}
# rotate the major face
main_face = self.cube_string[side_dict[side] * 9:side_dict[side] * 9 + 9]
main_face = self.rotate_slist(main_face, 1)
# create a list of the sub faces
sub_faces = sub_face_dict[side]
sub_face_list = []
for face in sub_faces:
sub_face = self.cube_string[side_dict[face[0]] * 9:side_dict[face[0]] * 9 + 9]
column_indices = sub_face_access_dict[face[1]]
if face[2] == 1:
column_indices.reverse()
sub_face_list += [sub_face[i] for i in column_indices]
# move the sub face list by 3
for face in range(3):
sub_face_list.insert(0, sub_face_list.pop(-1))
# rebuild the cube_string
# add back the main face
new_string = [i for i in self.cube_string]
for i, new in enumerate(main_face):
new_string[side_dict[side] * 9 + i] = new
# add back each of the sub faces
for i, face in enumerate(sub_faces):
column_indices = sub_face_access_dict[face[1]]
if face[2] == 1:
column_indices.reverse()
column = sub_face_list[i * 3:i * 3 + 3]
for j in range(3):
replace_index = side_dict[face[0]] * 9 + column_indices[j]
new_string[replace_index] = column[j]
self.cube_string = ""
for i in new_string:
self.cube_string += i
def make_cube_string(self, colours):
# restructure the list, so it is in the standard order for cubestrings.
ordered_colours = []
# starts with d, l, b, u, r, f
# is set to u, r, f, d, l, b
ordered_colours += self.rotate_slist(colours[3], 0)
ordered_colours += self.rotate_slist(colours[4], 1)
ordered_colours += self.rotate_slist(colours[5], 1)
ordered_colours += self.rotate_slist(colours[0], 1)
ordered_colours += self.rotate_slist(colours[1], 2)
ordered_colours += self.rotate_slist(colours[2], 2)
# Convert RGB to Lab
lab_pixels = [color.rgb2lab(np.array([[rgb]]))[0][0] for rgb in ordered_colours]
new = []
for i in lab_pixels:
new.append(
[i[0]*5,
i[1],
i[2]]
)
lab_pixels = new
fig1 = plt.figure()
fig2 = plt.figure()
# Add a 3D subplot
lab = fig1.add_subplot(111, projection='3d')
rgb = fig2.add_subplot(111, projection='3d')
# Plot the 3D scatter graph
lab.scatter(
[i[0] for i in lab_pixels] + [7000],
[i[1] for i in lab_pixels] + [7000],
[i[2] for i in lab_pixels] + [7000],
c=[(rgba[0] / 256, rgba[1] / 256, rgba[2] / 256) for rgba in ordered_colours] + [(0,0,0)],
)
rgb.scatter(
[i[0] for i in ordered_colours],
[i[1] for i in ordered_colours],
[i[2] for i in ordered_colours],
c=[(rgba[0] / 256, rgba[1] / 256, rgba[2] / 256) for rgba in ordered_colours],
)
plt.autoscale = False
# plt.show()
# Display the graph
print(lab_pixels)
# Apply k-means clustering
# kmeans = KMeans(n_clusters=6, random_state=42)
# kmeans.fit(lab_pixels)
# labels = kmeans.labels_
def remove_colour_references(colour_indices, dist_sort, dist_points):
# remove all items in the two distance lists referencing either of these colours as colour2
r_num = 0
for i in range(len(dist_sort)):
for j in colour_indices:
if dist_sort[i - r_num]['col1'] == j:
dist_sort.pop(i - r_num)
r_num += 1
break
for i in dist_points:
for remove_index in colour_indices:
for d, data_entry in enumerate(i):
if data_entry['col2'] == remove_index:
i.pop(d)
break
return dist_sort, dist_points
# add indices to ordered_colours for later
ordered_lab = [{'ind': i, 'val': colour} for i, colour in enumerate(lab_pixels)]
groups = []
# generate a list of the distances between all the colours
# stores the distances from the found points to all the remaining points
colour_distances_sorted = [] # sorted in terms of distances - used for initial search
colour_distances_points = [] # sorted in terms of points - used for searching distances
# each item looks like: (source_point_index, look_point_index, distance)
for c1i, colour1 in enumerate(ordered_lab):
colour_distances_points.append([])
for c2i, colour2 in enumerate(ordered_lab):
distance = ( # don't bother with the square root because it's too hard and we don't really need it
math.pow(colour1['val'][0]-colour2['val'][0], 2) +
math.pow(colour1['val'][1]-colour2['val'][1], 2) +
math.pow(colour1['val'][2]-colour2['val'][2], 2)
)
if distance != 0: # if the distance is zero then we are comparing a point to itself.
item = {'col1': c1i, 'col2': c2i, 'dist': distance}
colour_distances_sorted.append(item)
colour_distances_points[-1].append(item)
# sort the dist-points list
colour_distances_points[-1].sort(key=lambda row: row['dist'])
colour_distances_sorted.sort(key=lambda row: row['dist'])
for i in range(6):
# my janky grouping algorithm
# first, get the first and second colour from colour_distances
new_group = [
colour_distances_sorted[0]['col1'],
colour_distances_sorted[0]['col2']
]
# remove references of colours from the new group
colour_distances_sorted, colour_distances_points = \
remove_colour_references(new_group, colour_distances_sorted, colour_distances_points)
# loop until we find 9 colours
for _ in range(7):
closest_points = []
for a in new_group:
closest_points.append(colour_distances_points[a][0])
closest_points.sort(key=lambda row: row['dist'])
new_group.append(closest_points[0]['col2'])
colour_distances_sorted, colour_distances_points = \
remove_colour_references([new_group[-1]], colour_distances_sorted, colour_distances_points)
groups.append(new_group)
# convert the group indices into a cube_string
cube_nums = ['n' for _ in range(6 * 9)]
for i, group in enumerate(groups):
for item in group:
cube_nums[item] = i
print(cube_nums)
# # pack the list into a string
# self.cube_string = ""
# for i in cube_string:
# self.cube_string += i
# print(self.cube_string)
# converts the numbered labels into a cube string
cube_string = ['n' for _ in range(6 * 9)]
labels_lookup = {
cube_nums[9 * 0 + 4]: 'U',
cube_nums[9 * 1 + 4]: 'R',
cube_nums[9 * 2 + 4]: 'F',
cube_nums[9 * 3 + 4]: 'D',
cube_nums[9 * 4 + 4]: 'L',
cube_nums[9 * 5 + 4]: 'B',
}
print(labels_lookup)
for i, label in enumerate(cube_nums):
cube_string[i] = labels_lookup[label]
self.cube_string = ""
for i in cube_string:
self.cube_string += i
print(self.cube_string)
# Define the data
colors = ordered_colours
labels = cube_string
num_boxes = len(colors)
rows = 9
cols = num_boxes // rows
# Create a figure and axis
fig, ax = plt.subplots(rows, cols, figsize=(8, 4))
# Flatten the axis if it's a single row or column
if rows == 1 or cols == 1:
ax = ax.flatten()
# Plot each box with its corresponding color and label
for i in range(rows):
for j in range(cols):
ax[i][j].add_patch(
plt.Rectangle((0, 0), 1, 1,
color=[a/256 for a in colors[i*cols + j]],
alpha=0.7))
ax[i][j].text(0.5, 0.5, labels[i*cols + j], fontsize=20, ha='center', va='center', color='white')
# Remove the axis
ax[i][j].axis('off')
# Adjust layout
plt.tight_layout()
# Show the plot
plt.show()
#print(kociemba.solve(self.cube_string))